#include "rtda/heap/method_descriptor_parser.h"
#include <algorithm>

namespace jvm {
    namespace heap {

        std::unique_ptr<MethodDescriptor> MethodDescriptorParser::parse(const std::string& descriptor) {
            raw = descriptor;
            parsed = std::make_unique<MethodDescriptor>();
            offset = 0;

            startParams();
            parseParamTypes();
            endParams();
            parseReturnType();
            finish();

            return std::move(parsed);
        }

        void MethodDescriptorParser::startParams() {
            if (readUint8() != '(') {
                causePanic();
            }
        }

        void MethodDescriptorParser::endParams() {
            if (readUint8() != ')') {
                causePanic();
            }
        }

        void MethodDescriptorParser::finish() {
            if (offset != raw.length()) {
                causePanic();
            }
        }

        void MethodDescriptorParser::causePanic() {
            throw std::runtime_error("BAD descriptor: " + raw);
        }

        uint8_t MethodDescriptorParser::readUint8() {
            if (offset >= raw.length()) {
                causePanic();
            }
            return raw[offset++];
        }

        void MethodDescriptorParser::unreadUint8() {
            if (offset == 0) {
                causePanic();
            }
            offset--;
        }

        void MethodDescriptorParser::parseParamTypes() {
            while (true) {
                std::string t = parseFieldType();
                if (!t.empty()) {
                    parsed->addParameterType(t);
                }
                else {
                    break;
                }
            }
        }

        void MethodDescriptorParser::parseReturnType() {
            if (readUint8() == 'V') {
                parsed->returnType = "V";
                return;
            }

            unreadUint8();
            std::string t = parseFieldType();
            if (!t.empty()) {
                parsed->returnType = t;
                return;
            }

            causePanic();
        }

        std::string MethodDescriptorParser::parseFieldType() {
            switch (readUint8()) {
            case 'B': return "B";
            case 'C': return "C";
            case 'D': return "D";
            case 'F': return "F";
            case 'I': return "I";
            case 'J': return "J";
            case 'S': return "S";
            case 'Z': return "Z";
            case 'L': return parseObjectType();
            case '[': return parseArrayType();
            default:
                unreadUint8();
                return "";
            }
        }

        std::string MethodDescriptorParser::parseObjectType() {
            std::string unread = raw.substr(offset);
            size_t semicolonIndex = unread.find(';');
            if (semicolonIndex == std::string::npos) {
                causePanic();
                return "";
            }

            size_t objStart = offset - 1;
            size_t objEnd = offset + semicolonIndex + 1;
            offset = objEnd;
            return raw.substr(objStart, objEnd - objStart);
        }

        std::string MethodDescriptorParser::parseArrayType() {
            size_t arrStart = offset - 1;
            parseFieldType();
            size_t arrEnd = offset;
            return raw.substr(arrStart, arrEnd - arrStart);
        }

        std::unique_ptr<MethodDescriptor> parseMethodDescriptor(const std::string& descriptor) {
            MethodDescriptorParser parser;
            return parser.parse(descriptor);
        }

    } // namespace heap
} // namespace jvm